package com.sml.util;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.alibaba.fastjson.support.config.FastJsonConfig;
import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.client.HttpClient;
import org.apache.http.client.HttpRequestRetryHandler;
import org.apache.http.client.config.CookieSpecs;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.protocol.HttpContext;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.core.io.FileSystemResource;
import org.springframework.http.*;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.http.converter.json.GsonHttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.ResponseErrorHandler;
import org.springframework.web.client.RestTemplate;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Type;
import java.nio.charset.Charset;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Iterator;
import java.util.List;

@Slf4j
public class RestTemplateFactory {

  /**
   * 连接等待时长
   */
  public static final int connTimeout = 90000;
  /**
   * 读写数据时长
   */
  public static final int readTimeout = 90000;

  private static RestTemplate restTemplate;


  /**
   * 忽略Http证书验证方式
   */
  private static PoolingHttpClientConnectionManager enableSSL() {
    try {
      SSLContext sslContext = SSLContext.getInstance("TLS");
      //实现一个X509TrustManager接口，用于绕过验证，不用修改里面的方法
      X509TrustManager manager = new X509TrustManager() {
        @Override
        public void checkClientTrusted(X509Certificate[] x509Certificates, String s)
            throws CertificateException {

        }

        @Override
        public void checkServerTrusted(X509Certificate[] x509Certificates, String s)
            throws CertificateException {

        }

        @Override
        public X509Certificate[] getAcceptedIssuers() {
          return null;
        }
      };
      sslContext.init(null, new TrustManager[]{manager}, null);
      Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory>create()
          .register("http", PlainConnectionSocketFactory.INSTANCE)
          .register("https", new SSLConnectionSocketFactory(sslContext, new NoopHostnameVerifier()))
          .build();
      PoolingHttpClientConnectionManager poolingHttpClientConnectionManager = new PoolingHttpClientConnectionManager(
          socketFactoryRegistry);
      // 连接池最大并发连接数
      poolingHttpClientConnectionManager.setMaxTotal(500);
      // 单路由最大并发数
      poolingHttpClientConnectionManager.setDefaultMaxPerRoute(500);
      return poolingHttpClientConnectionManager;
    } catch (NoSuchAlgorithmException e) {
      e.printStackTrace();
    } catch (KeyManagementException e) {
      e.printStackTrace();
    }
    return null;
  }

  /**
   * 重试机制
   */
  private static HttpRequestRetryHandler myRetryHandler = new HttpRequestRetryHandler() {
    @Override
    public boolean retryRequest(IOException exception, int executionCount, HttpContext context) {
      // 不重试，不作处理
      return false;
    }
  };

  /**
   * 配置 httpClient 的配置
   */
  private static HttpClient httpClient = HttpClientBuilder.create()
      // 连接池
      .setConnectionManager(enableSSL())
      // Connection pool shut down
      .setConnectionManagerShared(true)
      // 重试机制
      .setRetryHandler(myRetryHandler)
      // RequestConfig
      .setDefaultRequestConfig(RequestConfig.custom()
          //连接上服务器(握手成功)的时间，超出该时间抛出connect timeout
          .setConnectTimeout(connTimeout)
          //服务器返回数据(response)的时间,读写超时，超过该时间抛出read timeout
          .setSocketTimeout(readTimeout)
          //从连接池中获取连接的超时时间，超过该时间未拿到可用连接，会抛出org.apache.http.conn.ConnectionPoolTimeoutException: Timeout waiting for connection from pool
          .setConnectionRequestTimeout(connTimeout)
          // cookie 策略
          .setCookieSpec(CookieSpecs.STANDARD_STRICT)
          // 先发送头预请求，请勿开启
          .setExpectContinueEnabled(false)
          .build()
      )
      //默认客户端连接重用策略   1.1 默认已开启
      //   .setConnectionReuseStrategy(new DefaultClientConnectionReuseStrategy())
      //保持长连接配置，需要在头添加Keep-Alive 1.1 默认已开启
      //   .setKeepAliveStrategy(new DefaultConnectionKeepAliveStrategy())
      .build();

  /**
   * 创建httpclent 连接池工厂，与初始化 restTemplate
   */
  private static void init() {
    // 创建http 连接工厂
    HttpComponentsClientHttpRequestFactory httpRequestFactory = new HttpComponentsClientHttpRequestFactory();
    httpRequestFactory.setHttpClient(httpClient);
    // 配置restTemplate 使用httpClient
    restTemplate = new RestTemplate(httpRequestFactory);
    //异常处理逻辑，根据自己的需要可以自定义异常处理
    restTemplate.setErrorHandler(new RestTemplateResponseErrorHandler());
    //换上fastjson
    List<HttpMessageConverter<?>> messageConverters = restTemplate.getMessageConverters();
    Iterator<HttpMessageConverter<?>> iterator = messageConverters.iterator();
    while (iterator.hasNext()) {
      HttpMessageConverter<?> converter = iterator.next();
      //原有的String是ISO-8859-1编码 去掉
      if (converter instanceof StringHttpMessageConverter) {
        iterator.remove();
      }
      //由于系统中默认有jackson 在转换json时自动会启用  但是我们不想使用它 可以直接移除
      if (converter instanceof GsonHttpMessageConverter
          || converter instanceof MappingJackson2HttpMessageConverter) {
        iterator.remove();
      }
    }
    messageConverters.add(new StringHttpMessageConverter(Charset.forName("utf-8")));
    FastJsonHttpMessageConverter fastJsonHttpMessageConverter = new FastJsonHttpMessageConverter();
    FastJsonConfig fastJsonConfig = new FastJsonConfig();
    fastJsonConfig.setSerializerFeatures(
        // 输出key时是否使用双引号,默认为true
        SerializerFeature.QuoteFieldNames,
        // Enum输出name()或者original,默认为false
        SerializerFeature.WriteEnumUsingToString,
        //是否输出值为null的字段,默认为false
        SerializerFeature.WriteMapNullValue,
        //消除对同一对象循环引用的问题，默认为false
        SerializerFeature.DisableCircularReferenceDetect
    );
    fastJsonHttpMessageConverter.setFastJsonConfig(fastJsonConfig);
    messageConverters.add(fastJsonHttpMessageConverter);

  }

  static {
    // 初始化执行
    enableSSL();
    init();
  }


  /**
   * 根据返回的Class进行请求
   *
   * @param url
   * @param headers
   * @param responseType
   * @param body
   * @param args
   * @return org.springframework.http.ResponseEntity<T>
   */
  public static <T> ResponseEntity<T> postForEntity(String url, HttpHeaders headers, Object body,
      Class<T> responseType, Object... args) {
    return exchange(url, HttpMethod.POST, headers, body, responseType, args);
  }

  /**
   * @return org.springframework.http.ResponseEntity<T>
   * @Author linlx
   * @Description 文件输出专用
   * @Date 2020/9/2 0002 下午 7:08
   * @Param [url, httpEntity, responseType]
   **/
  public static <T> ResponseEntity<T> postForEntity(String url,
      HttpEntity<MultiValueMap<String, Object>> httpEntity, Class<T> responseType) {
    ResponseEntity<T> responseEntity = restTemplate
        .exchange(url, HttpMethod.POST, httpEntity, responseType);
    return responseEntity;
  }

  /**
   * 根据包装的类型进行请求
   *
   * @param url
   * @param httpHeaders
   * @param body
   * @param typeReference
   * @param args
   * @return org.springframework.http.ResponseEntity<T>
   * @see ParameterizedTypeReference
   */
  public static <T> ResponseEntity<T> postForObject(String url, HttpHeaders httpHeaders,
      Object body, ParameterizedTypeReference<T> typeReference, Object... args) {
    return exchange(url, HttpMethod.POST, httpHeaders, body, typeReference, args);
  }

  /**
   * 根据 TypeReference类型 使用FastJson进行转换获取返回Class的类型
   *
   * @param url
   * @param headers
   * @param body
   * @param typeReference
   * @param args
   * @return org.springframework.http.ResponseEntity<T>
   */
  public static <T> ResponseEntity<T> postForEntity(String url, HttpHeaders headers, Object body,
      TypeReference typeReference, Object... args) {
    ResponseEntity<T> responseEntity = new ResponseEntity<T>(HttpStatus.BAD_REQUEST);
    if (typeReference != null) {
      responseEntity = exchange(url, HttpMethod.GET, headers, body, typeReference.getType(), args);
    }
    return responseEntity;
  }

  /**
   * 根据 Type类型 使用FastJson进行转换获取返回Class的类型
   *
   * @param url
   * @param headers
   * @param body
   * @param type
   * @param args
   * @return org.springframework.http.ResponseEntity<T>
   * @see ParameterizedTypeReference#getType()
   * @see TypeReference#getType()
   */
  public static <T> ResponseEntity<T> postForEntity(String url, HttpHeaders headers, Object body,
      Type type, Object... args) {
    return exchange(url, HttpMethod.POST, headers, body, type, args);
  }


  /**
   * 根据 ParameterizedTypeReference 类型获取信息
   *
   * @param url
   * @param httpHeaders
   * @param typeReference
   * @param args
   * @return <T> ResponseEntity<T>
   */
  public static <T> ResponseEntity<T> getForEntity(String url, HttpHeaders httpHeaders,
      ParameterizedTypeReference<T> typeReference, Object... args) {
    return exchange(url, HttpMethod.GET, httpHeaders, null, typeReference, args);
  }


  /**
   * 根据 Type类型 使用FastJson进行转换获取返回Class的类型
   *
   * @param url
   * @param headers
   * @param type
   * @param args
   * @return <T> ResponseEntity<T>
   * @see ParameterizedTypeReference#getType()
   * @see TypeReference#getType()
   */
  public static <T> ResponseEntity<T> getForEntity(String url, HttpHeaders headers, Type type,
      Object... args) {
    return exchange(url, HttpMethod.GET, headers, null, type, args);
  }


  /**
   * 根据 TypeReference类型 使用FastJson进行转换获取返回Class的类型
   *
   * @param url
   * @param headers
   * @param typeReference
   * @param args
   * @return org.springframework.http.ResponseEntity<T>
   */
  public static <T> ResponseEntity<T> getForEntity(String url, HttpHeaders headers,
      TypeReference typeReference, Object... args) {
    ResponseEntity<T> responseEntity = new ResponseEntity<T>(HttpStatus.BAD_REQUEST);
    if (typeReference != null) {
      responseEntity = exchange(url, HttpMethod.GET, headers, null, typeReference.getType(), args);
    }
    return responseEntity;
  }


  /**
   * 根据Class类型获取返回的信息
   *
   * @param url
   * @param headers
   * @param responseType
   * @param args
   * @return org.springframework.http.ResponseEntity<T>
   */
  public static <T> ResponseEntity<T> getForEntity(String url, HttpHeaders headers,
      Class<T> responseType, Object... args) {
    return exchange(url, HttpMethod.GET, headers, null, responseType, args);
  }


  private static <T> ResponseEntity<T> exchange(String url, HttpMethod method, HttpHeaders headers,
      Object body, Class<T> responseType, Object... args) {
    headers = resolveHttpHeaders(headers);
    HttpEntity<Object> httpEntity = new HttpEntity<Object>(body, headers);
    ResponseEntity<T> responseEntity = restTemplate
        .exchange(url, method, httpEntity, responseType, args);
    return responseEntity;
  }


  private static <T> ResponseEntity<T> exchange(String url, HttpMethod httpMethod,
      HttpHeaders headers, Object body, ParameterizedTypeReference<T> typeReference,
      Object... args) {
    headers = resolveHttpHeaders(headers);
    HttpEntity<Object> httpEntity = new HttpEntity<Object>(body, headers);
    ResponseEntity<T> responseEntity = restTemplate
        .exchange(url, httpMethod, httpEntity, typeReference, args);
    return responseEntity;
  }

  /**
   * 这里使用自己通过 FastJson进行数据信息的转换
   *
   * @see ParameterizedTypeReference#getType()
   * @see TypeReference#getType()
   */
  private static <T> ResponseEntity<T> exchange(String url, HttpMethod httpMethod,
      HttpHeaders headers, Object body, Type type, Object... args) {
    headers = resolveHttpHeaders(headers);
    HttpEntity<Object> httpEntity = new HttpEntity<Object>(body, headers);
    ResponseEntity<String> responseEntity = restTemplate
        .exchange(url, httpMethod, httpEntity, String.class, args);
    if (StringUtils.isNotEmpty(responseEntity.getBody()) && type != null) {
      return new ResponseEntity<T>(JSON.parseObject(responseEntity.getBody(), type),
          responseEntity.getHeaders(), responseEntity.getStatusCode());
    }
    return new ResponseEntity<T>(HttpStatus.BAD_REQUEST);
  }

  /**
   * 没有就设置默认的 HttpHeaders
   *
   * @see HttpHeaders#setContentType(MediaType)
   * @see MediaType#APPLICATION_JSON
   * @see MediaType#APPLICATION_JSON_UTF8 (部分版本不支持)
   */
  private static HttpHeaders resolveHttpHeaders(HttpHeaders headers) {
    if (headers == null) {
      headers = new HttpHeaders();
      headers.set("Content-Type", "application/json;charset=UTF-8");
      headers.set("Accept", MediaType.APPLICATION_JSON_UTF8_VALUE);
    }
    return headers;
  }

  static class RestTemplateResponseErrorHandler implements ResponseErrorHandler {

    @Override
    public boolean hasError(ClientHttpResponse response) throws IOException {
      if (HttpStatus.OK.equals(HttpStatus.resolve(response.getRawStatusCode()))) {
        return false;
      }
      return true;
    }

    @Override
    public void handleError(ClientHttpResponse response) throws IOException {
      HttpStatus statusCode = HttpStatus.resolve(response.getRawStatusCode());
      log.debug("HTTP请求异常：{}", statusCode);
    }
  }
}

